package org.dreamwork.jasmine2.util;

import org.dreamwork.jasmine2.javac.IClasspathBuilder;
import org.dreamwork.util.ReferenceUtil;
import org.dreamwork.util.StringUtil;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServlet;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashSet;
import java.util.Set;

/**
 * Created with IntelliJ IDEA.
 * User: seth.yang
 * Date: 13-7-31
 * Time: 下午2:36
 */
public class JBossClasspathBuilder implements IClasspathBuilder {
    private Set<URL> set = new HashSet<> ();
    private ServletContext application;
    private boolean parsed;
    private StringBuilder builder = new StringBuilder ();

    JBossClasspathBuilder (ServletContext application) {
        this.application = application;
    }

    @Override
    public String getClasspath () throws IOException {
        if (parsed) return builder.toString ();
        ClassLoader bcl = HttpServlet.class.getClassLoader ();
        if (bcl instanceof URLClassLoader) { // jboss 4.x or lower
            getJboss4ClassPath (bcl);
        } else { // jboss 5.x or higher
            getJboss5ClassPath (bcl);
        }
        parsed = true;
        return builder.toString ();
    }

    @Override
    public Set<URL> getClasspathUrls () throws IOException {
        if (!parsed) getClasspath ();
        return set;
    }

    private void getJboss5ClassPath (Object bcl) throws IOException {
        try {
            Object loader = get (bcl, "loader");
            Object delegate = get (loader, "delegate");
            Object roots = get (delegate, "roots");
            int length = Array.getLength (roots);
            for (int i = 0; i < length; i ++) {
                Object element = Array.get (roots, i);
                Object handler = get (element, "handler");
                delegate = get (handler, "delegate");
                if (delegate == null) continue;
                URL url = (URL) get (delegate, "url");
                if (url == null) continue;
                String protocol = url.getProtocol ();
                if ("file".equalsIgnoreCase (protocol)) {
                    set.add (url);
                    if (builder.length () > 0) builder.append (File.pathSeparatorChar);
                    builder.append (new File (url.toURI ()));
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace ();
        }

        String path = application.getRealPath ("/");
        if (!StringUtil.isEmpty (path)) {
            File file = new File (path, "/WEB-INF/lib");
            File[] jars = file.listFiles (new FileFilter () {
                @Override
                public boolean accept (File file) {
                    return file.getName ().endsWith (".jar");
                }
            });
            if (jars != null) for (File jar : jars) {
                set.add (jar.toURI ().toURL ());
                if (builder.length () > 0) builder.append (File.pathSeparatorChar);
                builder.append (jar.getCanonicalPath ());
            }
            file = new File (path, "/WEB-INF/classes");
            set.add (file.toURI ().toURL ());
            if (builder.length () > 0) builder.append (File.pathSeparatorChar);
            builder.append (file.getCanonicalPath ());
        }
    }

    @SuppressWarnings ("unchecked")
    private void getJboss4ClassPath (Object bcl) {
        try {
            Object repository = get (bcl, "repository");
            Set<URL> classLoaderURLs = (Set<URL>) get (repository, "classLoaderURLs");
            if (classLoaderURLs != null) {
                set.addAll (classLoaderURLs);
                for (URL url : classLoaderURLs) {
                    if (builder.length () > 0) builder.append (File.pathSeparatorChar);
                    File file = new File (url.toURI ());
                    builder.append (file.getCanonicalPath ());
                }
            }

            DefaultClasspathBuilder dcb = new DefaultClasspathBuilder ();
            set.addAll (dcb.getClasspathUrls ());
            if (builder.length () > 0) builder.append (File.pathSeparatorChar);
            builder.append (dcb.getClasspath ());
        } catch (Exception ex) {
            ex.printStackTrace ();
        }
    }

    private Object get (Object bean, String fieldName) {
        if (bean == null) return null;
        Field field = ReferenceUtil.findField (bean.getClass (), fieldName);

        if (field == null) return null;

        if (!field.isAccessible ())
            field.setAccessible (true);

        try {
            return field.get (bean);
        } catch (IllegalAccessException ex) {
            throw new RuntimeException (ex);
        }
    }
}